package com.gitee.wsl.platform.exception

import com.gitee.wsl.platform.platformPrintErr
import kotlinx.atomicfu.AtomicRef
import kotlinx.atomicfu.atomic


fun Throwable.handleReaktiveError(onError: ((Throwable) -> Unit)? = null) {
    val error = this
    error.throwIfFatal()

    if (onError == null) {
        handleError(error)
    } else {
        handleError(error, onError)
    }
}

private fun handleError(error: Throwable) {
    try {
        reaktiveUncaughtErrorHandler(error)
    } catch (errorDeliveryException: Throwable) {
        errorDeliveryException.throwIfFatal()
        printErrors("Error delivering uncaught error", error, errorDeliveryException)
    }
}

private fun handleError(error: Throwable, onError: (Throwable) -> Unit) {
    try {
        onError(error)
    } catch (errorHandlerException: Throwable) {
        errorHandlerException.throwIfFatal()
        printErrors("onError callback failed", error, errorHandlerException)

        try {
            reaktiveUncaughtErrorHandler(CompositeException(error, errorHandlerException))
        } catch (errorDeliveryException: Throwable) {
            errorDeliveryException.throwIfFatal()
            printErrors("Error delivering uncaught error", error, errorDeliveryException)
        }
    }
}

private fun printErrors(message: String, outerError: Throwable, innerError: Throwable) {
    platformPrintErr("$message ($outerError): $innerError")
    outerError.printStackTrace()
    innerError.printStackTrace()
}

fun Throwable.throwIfFatal() {
    if (isFatal()) {
        throw this
    }
}

@Suppress("ObjectPropertyName")
private val _reaktiveUncaughtErrorHandler: AtomicRef<(Throwable) -> Unit> =
    atomic(createDefaultUncaughtErrorHandler())

var reaktiveUncaughtErrorHandler: (Throwable) -> Unit
    get() = _reaktiveUncaughtErrorHandler.value
    set(value) {
        _reaktiveUncaughtErrorHandler.value = value
    }

fun resetReaktiveUncaughtErrorHandler() {
    reaktiveUncaughtErrorHandler = createDefaultUncaughtErrorHandler()
}

internal expect fun Throwable.isFatal(): Boolean


internal expect fun createDefaultUncaughtErrorHandler(): (Throwable) -> Unit
